<?php

/**
 * 路由解析器
 *
 * @author JiangJian <silverd@sohu.com>
 * $Id: Router.php 115 2012-10-08 16:50:04Z silverd30@gmail.com $
 */

class Core_Router
{
    /**
     * 解析当前 PATH_INFO
     * 返回控制器、方法名、附加参数
     *
     * @params string $pathInfo _directory/controller/action
     * @return array $dispatchInfo
     */
    public static function parse($pathInfo = null)
    {
        // 路由配置
        $routerCfg = Core_Config::load('router');

        // 缺省 PATH_INFO
        if ($pathInfo === null) {
            $pathInfo = self::getPathInfo();
        }

        // 将 PATH_INFO 切割成数组
        $pathInfos = $pathInfo ? explode('/', $pathInfo) : array();

        // 缺省路由
        if (!$pathInfos) {
            return $routerCfg['DefaultDispatchInfo'];
        }

        // 自定义路由规则 Rewrite
        $pathInfos = self::_customRules($pathInfos, $routerCfg['CustomRewriteRules']);

        // 将 controller 以及所在各级子目录转换成类名
        $controllerDir = array();
        foreach ($pathInfos as $key => $segment) {
            // 首字符如果是下划线，即表示目录
            if ($segment[0] != '_') {
                break;
            }
            $controllerDir[] = self::_camelize($segment);
            unset($pathInfos[$key]);
        }

        $controller = array_shift($pathInfos);
        $controller = ($controllerDir ? implode('_', $controllerDir) . '_' : '')
                    . ($controller ? self::_camelize($controller) : 'Index');

        $action     = array_shift($pathInfos);
        $action     = $action ? lcfirst(self::_camelize($action)) : 'index';

        // 解析GET参数，例如:
        // _directory/controller/action/k1/v1/k2/v2
        // 从 action 后的 key1 开始为 k/v 组合参数串
        // 因为前面已经两次 array_shift 弹出数组头元素
        // 所以此时的 $pathInfos 数组值已经是： array(k1, v1, k2, v2)，那么再转为关联数组即可 array(k1 => v1, k2 => v2)
        $params = $pathInfos ? Helper_Array::assoc($pathInfos) : array();

        return array(
            'controller' => $controller,
            'action'     => $action,
            'params'     => $params,
        );
    }

    /**
     * 获取 URL PATH_INFO
     *
     * @return string
     */
    public static function getPathInfo()
    {
        $pathInfo = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';

        // 当 Nginx 没有配置 PATH_INFO 时的兼容读取（注意修改 rewrite 规则）
        // 访问时必须隐藏 index.php 即通过 http://xianglephp/hello 访问
        if (!$pathInfo) {
            $pathInfo = isset($_GET['PATH_INFO']) ? $_GET['PATH_INFO'] : '';
        }

        return $pathInfo ? trim($pathInfo, '/') : '';
    }

    /**
     * 命令行模式下获取 PATH_INFO
     *
     * @return string
     */
    public static function getCliPathInfo()
    {
        return isset($_SERVER['argv'][1]) ? trim($_SERVER['argv'][1], '/') : '';
    }

    /**
     * 设置 URL PATH_INFO
     *
     * @param pathInfo
     */
    public static function setPathInfo($pathInfo)
    {
        $_SERVER['PATH_INFO'] = $pathInfo;
    }

    /**
     * 自定义路由规则配置 Rewrite
     * uses Conf/router.conf.php
     *
     * @param array $uris
     * @return array
     */
    private static function _customRules($uris, $rules)
    {
        if (!$rules) {
            return $uris;
        }

        // 将 $uris 数组还原成字符串 _directory/controller/action
        $uri = implode('/', $uris);

        // 直接命中匹配（完全匹配）
        if (isset($rules[$uri])) {
            return explode('/', $rules[$uri]);
        }

        // 正则命中匹配
        foreach ($rules as $key => $val) {
            // Does the RegEx match?
            if (preg_match('#^' . $key . '$#', $uri)) {
                // Do we have a back-reference?
                if (strpos($val, '$') !== false && strpos($key, '(') !== false) {
                    $val = preg_replace('#^' . $key . '$#', $val, $uri);
                }

                return explode('/', $val);
            }
        }

        return $uris;
    }

    /**
     * 驼峰形式转换：abc-xyz => AbcXyz
     *
     * @param string $word
     * @return string
     */
    private static function _camelize($word)
    {
        // 只允许小写字母、数字、减号
        $word = preg_replace('/[^a-z0-9\-]/', '', strtolower($word));

        if (strpos($word, '-') !== false) {
            $word = str_replace(' ', '', ucwords(str_replace('-', ' ', $word)));
        } else {
            $word = ucfirst($word);
        }

        return $word;
    }

    /**
     * 驼峰还原成连接符：AbcXyz => abc-xyz
     *
     * @param string $word
     * @param string $split
     * @return string
     */
    public static function hyphen($word, $split = '-')
    {
        return strtolower(preg_replace('/(?<=\\w)([A-Z])/', $split . '\\1', $word));
    }
}